{{!

  Copyright (c) Facebook, Inc. and its affiliates.

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.

}}

    use async_trait::async_trait;
    use std::marker::PhantomData;{{!
}}{{#program:services}}

    pub struct {{service:name}}<'mock> {{>lib/block}}
        {{#service:extends}}
        pub parent: {{service:package}}::mock::{{service:name}}<'mock>,
        {{/service:extends}}
        {{#service:functions}}{{^function:any_streams?}}
        pub {{function:name}}: {{service:snake}}::{{function:name}}<'mock>,
        {{/function:any_streams?}}{{/service:functions}}
        _marker: PhantomData<&'mock ()>,
    }

    impl dyn super::client::{{service:name}} {
        pub fn mock<'mock>() -> {{service:name}}<'mock> {
            {{service:name}} {
                {{#service:extends}}
                parent: {{service:package}}::client::{{service:name}}::mock(),
                {{/service:extends}}
                {{#service:functions}}{{^function:any_streams?}}
                {{function:name}}: {{!
                    }}{{service:snake}}::{{function:name}}::unimplemented(),
                {{/function:any_streams?}}{{/service:functions}}
                _marker: PhantomData,
            }
        }
    }

    #[async_trait]
    impl<'mock> super::client::{{service:name}} for {{service:name}}<'mock> {{>lib/block}}{{!
        }}{{#service:functions}}{{^function:any_streams?}}
        fn {{function:name}}(
            &self,{{!
            }}{{#function:args}}
            arg_{{field:name}}: {{>lib/arg}},{{!
            }}{{/function:args}}
        ) -> std::pin::Pin<Box<dyn std::future::Future<Output = std::result::Result<{{!
            }}{{#function:returnType}}{{>lib/type}}{{/function:returnType}}, {{!
            }}{{program:crate}}::errors::{{service:snake}}::{{function:upcamel}}Error{{!
        }}>> + Send + 'static>> {
            let mut closure = self.{{function:name}}.closure.lock().unwrap();
            let closure: &mut dyn FnMut({{!
                }}{{#function:args}}{{!
                }}{{#field:type}}{{>lib/type}}{{/field:type}}{{!
                }}{{^last?}}, {{/last?}}{{!
                }}{{/function:args}}{{!
            }}) -> _ = &mut **closure;
            Box::pin(futures::future::ready(closure({{!
                }}{{#function:args}}{{!
                }}arg_{{field:name}}{{#field:type}}{{!
                    }}{{#type:string?}}.to_owned(){{/type:string?}}{{!
                    }}{{#type:list?}}.to_owned(){{/type:list?}}{{!
                    }}{{^type:string?}}{{^type:list?}}{{!
                        }}.clone(){{!
                    }}{{/type:list?}}{{/type:string?}}{{!
                }}{{/field:type}}{{!
                }}{{^last?}}, {{/last?}}{{!
                }}{{/function:args}}{{!
            }})))
        }{{!
        }}{{/function:any_streams?}}{{/service:functions}}
    }

    {{#service:extendedServices}}
    #[async_trait]
    impl<'mock> {{!
        }}{{#extendedService:service}}{{!
        }}AsRef<dyn {{extendedService:packagePrefix}}::client::{{service:name}} + 'mock> {{!
        }}{{/extendedService:service}}{{!
        }}for {{service:name}}<'mock>
    {
        {{#extendedService:service}}
        fn as_ref(&self) -> &(dyn {{extendedService:packagePrefix}}::client::{{service:name}} + 'mock) {
            self
        }
        {{/extendedService:service}}
    }

    {{/service:extendedServices}}
    mod {{service:snake}} {{>lib/block}}{{!
        }}{{#service:functions?}}
        use std::sync::Mutex;{{!
        }}{{/service:functions?}}{{!

        }}{{#service:functions}}{{^function:any_streams?}}

        pub struct {{function:name}}<'mock> {
            pub(super) closure: Mutex<Box<
                dyn FnMut({{!
                    }}{{#function:args}}{{!
                    }}{{#field:type}}{{>lib/type}}{{/field:type}}{{!
                    }}{{^last?}}, {{/last?}}{{!
                    }}{{/function:args}}{{!
                }}) -> Result<
                    {{#function:returnType}}{{>lib/type}}{{/function:returnType}},
                    {{program:crate}}::errors::{{service:snake}}::{{function:upcamel}}Error,
                > + Send + Sync + 'mock,
            >>,
        }

        impl<'mock> {{function:name}}<'mock> {
            pub fn unimplemented() -> Self {
                {{function:name}} {
                    closure: Mutex::new(Box::new(|{{!
                        }}{{#function:args}}{{!
                        }}_: {{#field:type}}{{>lib/type}}{{/field:type}}{{!
                        }}{{^last?}}, {{/last?}}{{!
                        }}{{/function:args}}{{!
                    }}| panic!(
                        "{}::{} is not mocked",
                        "{{service:name}}",
                        "{{function:name}}",
                    ))),
                }
            }

            pub fn ret({{!
                }}&self, {{!
                }}value: {{#function:returnType}}{{>lib/type}}{{/function:returnType}}{{!
            }}) {
                self.mock(move |{{!
                    }}{{#function:args}}{{!
                    }}_: {{#field:type}}{{>lib/type}}{{/field:type}}{{!
                    }}{{^last?}}, {{/last?}}{{!
                    }}{{/function:args}}{{!
                }}| value.clone());
            }

            pub fn mock(&self, mut mock: impl FnMut({{!
                }}{{#function:args}}{{!
                }}{{#field:type}}{{>lib/type}}{{/field:type}}{{!
                }}{{^last?}}, {{/last?}}{{!
                }}{{/function:args}}{{!
            }}) -> {{#function:returnType}}{{>lib/type}}{{/function:returnType}}{{!
                }} + Send + Sync + 'mock) {{!
            }}{
                let mut closure = self.closure.lock().unwrap();
                *closure = Box::new(move |{{!
                    }}{{#function:args}}{{!
                    }}{{field:rust_name}}{{!
                    }}{{^last?}}, {{/last?}}{{!
                    }}{{/function:args}}{{!
                }}| Ok(mock({{!
                    }}{{#function:args}}{{!
                    }}{{field:rust_name}}{{!
                    }}{{^last?}}, {{/last?}}{{!
                    }}{{/function:args}}{{!
                }})));
            }

            pub fn throw<E>(&self, exception: E)
            where
                E: Into<{{program:crate}}::errors::{{service:snake}}::{{function:upcamel}}Error>,
                E: Clone + Send + Sync + 'mock,
            {
                let mut closure = self.closure.lock().unwrap();
                *closure = Box::new(move |{{!
                    }}{{#function:args}}{{!
                    }}_: {{#field:type}}{{>lib/type}}{{/field:type}}{{!
                    }}{{^last?}}, {{/last?}}{{!
                    }}{{/function:args}}{{!
                }}| Err(exception.clone().into()));
            }
        }{{!
        }}{{/function:any_streams?}}{{/service:functions}}
    }{{!
}}{{/program:services}}
{{!newline}}
